#pragma once

#include <type_traits>
#include <algorithm>
#include "utility.hpp"
#include "type_traits.hpp"

#include "impls/variant.ipp"

#include "config/config_begin.h"
namespace cpp
{
	//////////////////////////////////////////////////////////////////////////
	template <class... Types>
	class variant;

	namespace __variant
	{
		template<class V>
		struct _IsVariant :std::false_type {};

		template<class... Types>
		struct _IsVariant< variant<Types...> > :std::true_type {};
	}

	class bad_variant_access final :public std::exception
	{
	public:
		virtual char const* what() const override
		{
			return  "bad variant access";
		}
	};

	template <class T> struct variant_size; // undefined

	template <class T> struct variant_size<const T> :variant_size<T> {};

	template <class T> struct variant_size<volatile T> :variant_size<T> {};
	template <class T> struct variant_size<const volatile T> :variant_size<T> {};
	template <class T>
	constexpr const size_t variant_size_v = variant_size<T>::value;

	template <class... Types>
	struct variant_size<variant<Types...>> : std::integral_constant<size_t, sizeof...(Types)> { };


	template <size_t I, class T> struct variant_alternative; // undefined

	template <size_t I, class T>
	using variant_alternative_t = typename variant_alternative<I, T>::type;


	template <size_t I, class T>
	struct variant_alternative<I, const T>
	{
		using type = std::add_const_t<  variant_alternative_t<I, T> >;
	};

	template <size_t I, class T>
	struct variant_alternative<I, volatile T>
	{
		using type = std::add_volatile_t< variant_alternative_t<I, T> >;
	};

	template <size_t I, class T>
	struct variant_alternative<I, const volatile T>
	{
		using type = std::add_cv_t< variant_alternative_t<I, T> >;
	};

	template <size_t I, class... Types>
	struct variant_alternative< I, variant<Types...> >
	{
		static_assert(I < sizeof...(Types), "invalid index");
		using type = pack_element_t<I, Types...>;
	};

	namespace __variant
	{
		template <class... Types>
		using _VariantBaseT = __variant::_VariantBase<
			typename conjunction< typename std::is_trivially_destructible< Types>::type ... >::type,
			Types...
		>;
	}

	template <class... Types>
	class variant :public __variant::_VariantBaseT< Types...>
	{
		static_assert(sizeof...(Types),"Types... cannot be empty");

		static_assert(!cpp::disjunction< std::is_void<Types> ... >::value, "Types... cannot contain void");

		static_assert(!cpp::disjunction< std::is_reference<Types> ... >::value, "Types... cannot contain reference types");

		using pack_types = pack<Types...>;
		using base_type = __variant::_VariantBaseT< Types...>;

	public:
		template<bool _ = true, class = std::enable_if_t< _&&std::is_default_constructible< pack_element_t<0, pack_types> >::value > >
		constexpr variant() noexcept(std::is_nothrow_default_constructible< pack_element_t<0, pack_types> >::value)
			:base_type() {	}

		template<bool _ = true, class = std::enable_if_t< _&& conjunction< typename std::is_copy_constructible< Types>::type ... >::value  > >
		variant(const variant& other) : base_type(static_cast<const base_type &>(other)) {}

		template<bool _ = true, class = std::enable_if_t< _&& conjunction< typename std::is_move_constructible< Types>::type ... >::value > >
		variant(variant&& other) noexcept(conjunction< typename std::is_nothrow_move_constructible< Types>::type ... >::value)
			: base_type(std::move(static_cast<base_type &>(other))) {}

		template <class T, class = std::enable_if_t< !std::is_same< std::decay_t<T>, variant  >::value	>,
			std::size_t index = __variant::_OverloadIndexOf<T&&, Types...>::value,
			class = std::enable_if_t< std::is_constructible< pack_element_t< index, Types...>, T >::value >
		>
			constexpr variant(T&& value) noexcept(std::is_nothrow_constructible <
												  pack_element_t<index, Types...>, T&&>::value)
			:base_type(std::forward<T>(value))
		{

		}

		template <class T, class... Args, std::size_t index = __variant::_IndexOf<T, Types...>::value, class = std::enable_if_t< std::is_constructible< T, Args...>::value  > >
		constexpr explicit variant(in_place_type_t<T>, Args&&...args)
			:base_type(in_place<index>, std::forward<Args>(args)...)
		{

		}

		template <class T, class U, class... Args, std::size_t index = __variant::_IndexOf<T, Types...>::value, class = std::enable_if_t< std::is_constructible< T, std::initializer_list<U>&, Args...>::value  >>
		constexpr explicit variant(in_place_type_t<T>, std::initializer_list<U> il, Args&&...args)
			:base_type(in_place<index>, il, std::forward<Args>(args)...)
		{

		}

		template <std::size_t I, class... Args,
			class = std::enable_if_t < (I < sizeof...(Types)) >,
			class T = pack_element_t< I, Types... >,
			class = std::enable_if_t < std::is_constructible< T, Args... >::value  >
			>
			constexpr explicit variant(in_place_index_t<I>, Args&&...args)
			:base_type(in_place<I>, std::forward<Args>(args)...)
		{
		}

		template <std::size_t I, class U, class... Args,
			class = std::enable_if_t< (I < sizeof...(Types)) &&
			std::is_constructible< pack_element_t<I, pack_types>, std::initializer_list<U>&, Args...  >::value  > >
			constexpr explicit variant(in_place_index_t<I>, std::initializer_list<U> il, Args&&...args)
			:base_type(in_place<I>, il, std::forward<Args>(args)...)
		{

		}

		template <class Alloc, bool _ = true, class = std::enable_if_t<_&& std::is_default_constructible< pack_element_t<0, pack_types> >::value > >
		variant(std::allocator_arg_t, const Alloc& a)
			: base_type(std::allocator_arg, a)
		{}

		template <class Alloc, bool _ = true, class = std::enable_if_t< _&& conjunction< typename std::is_copy_constructible< Types>::type ... >::value  > >
		variant(std::allocator_arg_t, const Alloc& a, const variant& other)
			: base_type(std::allocator_arg, a, static_cast<const base_type &>(other))
		{}

		template <class Alloc, bool _ = true, class = std::enable_if_t< _&& conjunction< typename std::is_move_constructible< Types>::type ... >::value >>
		variant(std::allocator_arg_t, const Alloc& a, variant&& other)
			: base_type(std::allocator_arg, a, std::move(static_cast<base_type &>(other)))
		{}

		template <class Alloc, class T, std::size_t index = __variant::_OverloadIndexOf<T&&, Types...>::value,
			class = std::enable_if_t< std::is_constructible< pack_element_t< index, Types...>, T >::value >>
			variant(std::allocator_arg_t, const Alloc& a, T&& value)
			: base_type(std::allocator_arg, a, std::forward<T>(value))
		{}

		template <class Alloc, class T, class... Args, std::size_t index = __variant::_IndexOf<T, Types...>::value, class = std::enable_if_t< std::is_constructible< T, Args...>::value  > >
		variant(std::allocator_arg_t, const Alloc& a, in_place_type_t<T>, Args&&...args)
			: base_type(std::allocator_arg, a, in_place<index>, std::forward<Args>(args)...)
		{

		}

		template <class Alloc, class T, class U, class... Args,
			std::size_t index = __variant::_IndexOf<T, Types...>::value,
			class = std::enable_if_t< std::is_constructible< T, std::initializer_list<U>&, Args...>::value  >>
			variant(std::allocator_arg_t, const Alloc& a, in_place_type_t<T>, std::initializer_list<U> il, Args&&...args)
			:base_type(std::allocator_arg, a, in_place<index>, il, std::forward<Args>(args)...)
		{

		}

		template <class Alloc, size_t I, class... Args,
			class = std::enable_if_t < (I < sizeof...(Types)) >,
			class T = pack_element_t< I, Types... >,
			class = std::enable_if_t < std::is_constructible< T, Args... >::value  >>
			variant(std::allocator_arg_t, const Alloc& a, in_place_index_t<I>, Args&&...args)
			: base_type(std::allocator_arg, a, in_place<I>, std::forward<Args>(args)...)
		{

		}


		template <class Alloc, size_t I, class U, class... Args,
			class = std::enable_if_t< (I < sizeof...(Types)) &&
			std::is_constructible< pack_element_t<I, pack_types>, std::initializer_list<U>&, Args...  >::value  > >
			variant(std::allocator_arg_t, const Alloc& a, in_place_index_t<I>, std::initializer_list<U> il, Args&&...args)
			:base_type(std::allocator_arg, a, in_place<I>, il, std::forward<Args>(args)...)
		{

		}

		~variant() = default;

		template<bool _ = true, class = std::enable_if_t< _&&
			conjunction< typename std::is_copy_constructible< Types>::type ... >::value
			&& conjunction< typename std::is_move_constructible< Types>::type ... >::value
			&& conjunction< typename std::is_copy_assignable< Types>::type ... >::value
			>>
			variant& operator=(const variant& other)
		{
			if(this == &other)
			{
				return *this;
			}
			base_type::operator=(static_cast<base_type const &>(other));
			return *this;
		}

		template<bool _ = true, class = std::enable_if_t<_&&
			conjunction< typename std::is_move_constructible< Types>::type ... >::value
			&& conjunction< typename std::is_move_assignable< Types>::type ... >::value
			>>
			variant& operator=(variant&& other) noexcept(conjunction< typename std::is_nothrow_move_constructible< Types>::type ... >::value
														 &&conjunction< typename std::is_nothrow_move_assignable< Types>::type ... >::value)
		{
			if(this == &other)
			{
				return *this;
			}
			base_type::operator=(std::move(static_cast<base_type&>(other)));
			return *this;
		}


		template <class T, class = std::enable_if_t<!std::is_same< std::decay_t<T>, variant >::value>,
			class = std::enable_if_t<
			std::is_assignable< pack_element_t< __variant::_OverloadIndexOf<T&&, Types...>::value, Types...> &, T >::value&&
			std::is_constructible< pack_element_t< __variant::_OverloadIndexOf<T&&, Types...>::value, Types...>, T >::value > >
			variant& operator=(T&& value) noexcept(std::is_nothrow_assignable< pack_element_t< __variant::_OverloadIndexOf<T&&, Types...>::value, Types...> &, T&&>::value
												   && std::is_nothrow_constructible<pack_element_t< __variant::_OverloadIndexOf<T&&, Types...>::value, Types...>, T&&>::value)
		{
			base_type::operator=(std::forward<T>(value));
			return *this;
		}


		template <class T, class... Args,
			std::size_t index = __variant::_IndexOf<T, Types...>::value,
			class = std::enable_if_t<std::is_constructible<T, Args...>::value >>
			void emplace(Args&&... args)
		{
			base_type::template emplace<index>(std::forward<Args>(args)...);
		}

		template <class T, class U, class... Args,
			std::size_t index = __variant::_IndexOf<T, Types...>::value,
			class = std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args...>::value >>
			void emplace(std::initializer_list<U> il, Args&&...args)
		{
			base_type::template emplace<index>(il, std::forward<Args>(args)...);
		}

		template < std::size_t I, class... Args,
			class = std::enable_if_t<std::is_constructible< pack_element_t<I, Types...>, Args...>::value >>
			void emplace(Args&&...args)
		{
			base_type::template emplace<I>(std::forward<Args>(args)...);
		}

		template < std::size_t I, class U, class... Args,
			class = std::enable_if_t<std::is_constructible< pack_element_t<I, Types...>, std::initializer_list<U>&, Args...>::value >>
			void emplace(std::initializer_list<U> il, Args&&... args)
		{
			base_type::template emplace<I>(il, std::forward<Args>(args)...);
		}


		constexpr bool has_value() const noexcept { return base_type::_HasValue(); }

		constexpr std::size_t index() const noexcept { return base_type::_Index(); }

		constexpr bool valueless_by_exception() const noexcept { return !base_type::_HasValue(); }


		template<bool _ = true, class = std::enable_if_t<_&& conjunction< is_swappable<Types> ... >::value >>
		void swap(variant& other) noexcept(conjunction< is_nothrow_swappable<Types> ... >::value
										   && std::is_nothrow_move_constructible<variant>::value
										   && std::is_nothrow_move_assignable<variant>::value)
		{
			base_type::swap(static_cast<base_type&>(other));
		}


		template<std::size_t I>
		auto& _Get()
		{
			return	base_type::template  _Get<I>();
		}

		template<std::size_t I>
		auto& _Get() const
		{
			return	base_type::template _Get<I>();
		}

		constexpr auto _RawIndex() const noexcept { return base_type::_rawIndex(); }
	};


	namespace __variant
	{
		template <size_t I, class... Types>
		constexpr variant_alternative_t<I, variant<Types...>>&		unsafe_get(variant<Types...>& v)
		{
			return static_cast<variant_alternative_t<I, variant<Types...>>&>(v.template _Get<I>());
		}

		template <size_t I, class... Types>
		constexpr variant_alternative_t<I, variant<Types...>>&&		unsafe_get(variant<Types...>&& v)
		{
			return static_cast<variant_alternative_t<I, variant<Types...>>&&>(v.template _Get<I>());
		}

		template <size_t I, class... Types>
		constexpr variant_alternative_t<I, variant<Types...>> const&	unsafe_get(const variant<Types...>& v)
		{
			return static_cast<variant_alternative_t<I, variant<Types...>> const&>(v.template _Get<I>());
		}

		template <size_t I, class... Types>
		constexpr variant_alternative_t<I, variant<Types...>> const&&	unsafe_get(const variant<Types...>&& v)
		{
			return static_cast<variant_alternative_t<I, variant<Types...>> const&&>(v.template _Get<I>());
		}

	}

	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value >
	constexpr bool holds_alternative(const variant<Types...>& v) noexcept
	{
		return (v._RawIndex() == index);
	}

	//////////////////////////////////////////////////////////////////////////
	template <size_t I, class... Types>
	constexpr variant_alternative_t<I, variant<Types...>>&	get(variant<Types...>& v)
	{
		if(v._RawIndex() != I)
		{
			throw bad_variant_access();
		}
		return static_cast<variant_alternative_t<I, variant<Types...>>&>(v.template _Get<I>());
	}

	template <size_t I, class... Types>
	constexpr variant_alternative_t<I, variant<Types...>>&&		get(variant<Types...>&& v)
	{
		if(v._RawIndex() != I)
		{
			throw bad_variant_access();
		}
		return static_cast<variant_alternative_t<I, variant<Types...>>&&>(v.template _Get<I>());
	}

	template <size_t I, class... Types>
	constexpr variant_alternative_t<I, variant<Types...>> const&	get(const variant<Types...>& v)
	{
		if(v._RawIndex() != I)
		{
			throw bad_variant_access();
		}
		return static_cast<variant_alternative_t<I, variant<Types...>> const&>(v.template _Get<I>());
	}

	template <size_t I, class... Types>
	constexpr variant_alternative_t<I, variant<Types...>> const&&	get(const variant<Types...>&& v)
	{
		if(v._RawIndex() != I)
		{
			throw bad_variant_access();
		}
		return static_cast<variant_alternative_t<I, variant<Types...>> const&&>(v.template _Get<I>());
	}

	//////////////////////////////////////////////////////////////////////////
	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value >
	constexpr T& get(variant<Types...>& v)
	{
		return get<index>(v);
	}

	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value>
	constexpr T&& get(variant<Types...>&& v)
	{
		return get<index>(std::move(v));
	}

	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value>
	constexpr const T& get(const variant<Types...>& v)
	{
		return get<index>(v);
	}

	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value>
	constexpr const T&& get(const variant<Types...>&& v)
	{
		return get<index>(std::move(v));
	}


	template <size_t I, class... Types>
	constexpr std::add_pointer_t<variant_alternative_t<I, variant<Types...>>> get_if(variant<Types...>* v) noexcept
	{
		if(v && v->_RawIndex() == I)
		{
			return &__variant::unsafe_get<I>(*v);
		}
		return nullptr;
	}

	template <size_t I, class... Types>
	constexpr std::add_pointer_t<const variant_alternative_t<I, variant<Types...>>> get_if(const variant<Types...>* v) noexcept
	{
		if(v && v->_RawIndex() == I)
		{
			return &__variant::unsafe_get<I>(*v);
		}
		return nullptr;
	}


	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value, class = std::enable_if_t<!std::is_same< std::decay_t<T>, void>::value  >>
	constexpr std::add_pointer_t<T> get_if(variant<Types...>* v) noexcept
	{
		if(v && v->_RawIndex() == index)
		{
			return &__variant::unsafe_get<index>(*v);
		}
		return nullptr;
	}

	template <class T, class... Types, std::size_t index = __variant::_IndexOf<T, Types...>::value, class = std::enable_if_t<!std::is_same< std::decay_t<T>, void>::value  >>
	constexpr std::add_pointer_t<const T> get_if(const variant<Types...>* v) noexcept
	{
		if(v && v->_RawIndex() == index)
		{
			return &__variant::unsafe_get<index>(*v);
		}
		return nullptr;
	}

	//////////////////////////////////////////////////////////////////////////
	template <class... Types>
	constexpr bool operator==(const variant<Types...>& v, const variant<Types...>& w) noexcept
	{
		return (v.valueless_by_exception() && w.valueless_by_exception()) ||
			(v._RawIndex() == w._RawIndex() && __variant::_Equal(v, w));
	}
	template <class... Types>
	constexpr bool operator!=(const variant<Types...>& v, const variant<Types...>& w)noexcept
	{
		return (v._RawIndex() != w._RawIndex()) || (v.has_value() && __variant::_UnEqual(v, w));
	}
	template <class... Types>
	constexpr bool operator<(const variant<Types...>& v, const variant<Types...>& w)noexcept
	{
		return  (v._RawIndex() < w._RawIndex()) || (v._RawIndex() == w._RawIndex() && v.has_value() && __variant::Less(v, w));
	}

	template <class... Types>
	constexpr bool operator>(const variant<Types...>& v, const variant<Types...>& w)noexcept
	{
		return  (v._RawIndex() > w._RawIndex()) || (v._RawIndex() == w._RawIndex() && v.has_value() && __variant::_Greater(v, w));
	}
	template <class... Types>
	constexpr bool operator<=(const variant<Types...>& v, const variant<Types...>& w)noexcept
	{
		return  (v._RawIndex() < w._RawIndex()) || (v._RawIndex() == w._RawIndex() &&
			(v.valueless_by_exception() || __variant::_LessEqual(v, w)));
	}

	template <class... Types>
	constexpr bool operator>=(const variant<Types...>& v, const variant<Types...>& w) noexcept
	{
		return  (v._RawIndex() > w._RawIndex()) || (v._RawIndex() == w._RawIndex() &&
			(v.valueless_by_exception() || __variant::_GreaterEqual(v, w)));
	}

	namespace __variant
	{
		namespace details
		{
			template <class F, class Tuple, std::size_t... I>
			constexpr inline decltype(auto) _ApplyImpl(F&& f, Tuple&& t, std::index_sequence<I...>)
			{
				return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
			}
		}

		template <class F, class Tuple>
		constexpr inline decltype(auto) _Apply(F&& f, Tuple&& t)
		{
			return details::_ApplyImpl(std::forward<F>(f), std::forward<Tuple>(t),
									   std::make_index_sequence <std::tuple_size<std::decay_t<Tuple> >::value > {});
		}


		template<class Visitor, class Variant, class...Args>
		constexpr inline decltype(auto) _VisitVariant(Visitor&& v, Variant && u, Args&& ...args)
		{
			return __variant::_VisitVariantImpl(std::forward<Visitor>(v), std::forward<Variant>(u), u._RawIndex(), std::forward<Args>(args)...);
		}


		struct _CallVisitor
		{
			template<std::size_t I, class Variant, class Visitor>
			constexpr inline decltype(auto) invoke(Variant&& vt, Visitor&& v)
			{
				return std::invoke(std::forward<Visitor>(v), unsafe_get<I>(std::forward<Variant>(vt)));
			}
		};


		struct _MultiCallVisitor
		{
			template<std::size_t I, class Variant, class Visitor, class Values, class Variant1, class... Variants >
			constexpr decltype(auto) invoke(Variant&& vt, Visitor&& v,
											Values&& values,
											Variant1&& v1, Variants&&... vs)
			{

				return _VisitVariant(_MultiCallVisitor{},
									 std::forward<Variant1>(v1), std::forward<Visitor>(v),
									 std::tuple_cat(std::forward<Values>(values), std::forward_as_tuple(unsafe_get<I>(std::forward<Variant>(vt)))),
									 std::forward<Variants>(vs)...
				);
			}

			template<std::size_t I, class Variant, class Visitor, class Values>
			constexpr decltype(auto) invoke(Variant&& vt, Visitor&& v, Values&& values)
			{
				return do_invoke(std::forward<Visitor>(v), std::tuple_cat(values, std::forward_as_tuple(unsafe_get<I>(std::forward<Variant>(vt)))));
			}

			template<class Visitor, class Values >
			constexpr decltype(auto) do_invoke(Visitor&& v, Values&& values)
			{
				return _Apply(std::forward<Visitor>(v), std::move(values));
			}

		};

		template <class Visitor>
		constexpr inline decltype(auto) _Visit(Visitor&& v)
		{
			return std::invoke(std::forward<Visitor>(v));
		}

		template <class Visitor, class  Variant>
		constexpr inline decltype(auto) _Visit(Visitor&& v, Variant&& u)
		{
			if(u.valueless_by_exception()) { throw bad_variant_access(); }
			return _VisitVariant(_CallVisitor{}, std::forward<Variant>(u), std::forward<Visitor>(v));
		}

		constexpr inline bool _AnyOf(bool b) noexcept{ return b; }
		template<class... B>
		inline constexpr bool _AnyOf(bool b0, B... b){ if(b0){ return true; } return _AnyOf(b...); }


		template <class Visitor, class Variant, class... Variants>
		constexpr inline decltype(auto) _Visit(Visitor&& v, Variant&& v0, Variants&&... vs)
		{
			if(_AnyOf(v0.valueless_by_exception(), vs.valueless_by_exception()...)) { throw bad_variant_access(); }
			return _VisitVariant(_MultiCallVisitor{}, std::forward<Variant>(v0), std::forward<Visitor>(v), std::tuple<>{}, std::forward<Variants>(vs)...);
		}
	}

	template <class Visitor, class... Variants>
	constexpr inline decltype(auto) visit(Visitor&& v, Variants&&... vs)
	{
		return __variant::_Visit(std::forward<Visitor>(v), std::forward<Variants>(vs)...);
	}

	struct monostate {};
	constexpr inline bool operator<(monostate, monostate) noexcept { return false; }
	constexpr inline bool operator>(monostate, monostate) noexcept { return false; }
	constexpr inline bool operator<=(monostate, monostate) noexcept { return true; }
	constexpr inline bool operator>=(monostate, monostate) noexcept { return true; }
	constexpr inline bool operator==(monostate, monostate) noexcept { return true; }
	constexpr inline bool operator!=(monostate, monostate) noexcept { return false; }


	template <class... Types>
	void swap(variant<Types...>& lhs, variant<Types...>& rhs) noexcept(lhs.swap(rhs))
	{
		lhs.swap(rhs);
	}
}

namespace std
{
	template <class... Types>
	struct hash<cpp::variant<Types...>>
	{
		size_t operator()(const cpp::variant<Types...>& val) const noexcept
		{
			auto index = val.index();
			if(index == cpp::variant_npos)
			{
				return 12321;
			}
			return std::hash<size_t>{}(index) ^ cpp::visit([](auto&& v)
			{
				return std::hash<std::remove_cv_t<std::remove_reference_t<  decltype(v)>> >{}(v);
			}, val);
		}
	};

	template <>
	struct hash<cpp::monostate>
	{
		size_t operator()(const cpp::monostate&) const noexcept
		{
			return 2333;
		}
	};

	template <class... Types, class Alloc>
	struct uses_allocator< cpp::variant<Types...>, Alloc> : std::true_type { };
}

#include "config/config_end.h"
